Skip to content
This repository was archived by the owner on Dec 2, 2025. It is now read-only.

Feat pool activity feed#270

Open
respp wants to merge 6 commits intotrustbridgecr:developfrom
respp:feat-pool-activity-feed
Open

Feat pool activity feed#270
respp wants to merge 6 commits intotrustbridgecr:developfrom
respp:feat-pool-activity-feed

Conversation

@respp
Copy link
Contributor

@respp respp commented Sep 26, 2025

pr for TrustBridge - Close Issue #250

Pull Request Information

implements a comprehensive Pool History/Activity Feed feature for the TrustBridge dApp, providing real-time transaction monitoring and user-friendly activity tracking.

The feature enables users to view, filter, and interact with pool transactions including supplies, borrows, liquidations, and repayments with full transparency and real-time updates.

🌀 Summary of Changes

  • feat: add activity entity types and interfaces - Complete TypeScript type system with enums, interfaces, and component props for the activity feed feature
  • feat: implement transaction monitoring helper - Core business logic for parsing Stellar transactions, categorizing activities, and formatting data with support for Blend Protocol events
  • feat: add usePoolActivity hook for activity data management - Real-time data fetching hook with WebSocket/polling fallback, filtering, pagination, and robust error handling
  • feat: create ActivityItem component for individual transaction display - Rich UI component with expandable details, visual icons, and links to Stellar Explorer
  • feat: implement ActivityFeed component with real-time updates - Complete activity feed interface with advanced filtering, search, infinite scroll, and real-time status indicators
  • feat: integrate activity feed into marketplace page - Seamless integration as a new tab in the existing marketplace interface while preserving all existing functionality

🎯 Technical Implementation Details

New Files Created:

  • src/@types/activity.entity.ts - TypeScript interfaces and enums
  • src/helpers/transaction-monitoring.helper.ts - Transaction parsing and categorization logic
  • src/hooks/usePoolActivity.ts - Real-time data management hook
  • src/components/modules/marketplace/ui/components/ActivityItem.tsx - Individual transaction component
  • src/components/modules/marketplace/ui/components/ActivityFeed.tsx - Main activity feed component

Modified Files:

  • src/components/modules/marketplace/ui/pages/MarketplacePage.tsx - Added activity feed tab integration

Key Features Implemented:

  • ✅ Real-time activity updates without page refresh
  • ✅ Advanced filtering and search capabilities
  • ✅ Links to Stellar Expert explorer for transaction verification
  • ✅ Performance optimized for large datasets
  • ✅ Mobile responsive design
  • ✅ Accessibility compliance (WCAG 2.1)
  • ✅ Error handling and retry mechanisms
  • ✅ TypeScript type safety throughout

Architecture:

  • Data Layer: usePoolActivity hook with WebSocket/polling fallback
  • Business Logic: TransactionMonitoringHelper for parsing and categorization
  • UI Components: ActivityFeed and ActivityItem with rich interactions
  • Integration: Seamless tab integration in existing marketplace

This implementation follows the technical PRD specifications and provides a production-ready activity feed feature that enhances user experience and transparency in the TrustBridge dApp.

This pull request will close # [Issue Number] upon merging.


🎉 Thank you for reviewing this PR! 🎉

Summary by CodeRabbit

  • New Features
    • Added Activity tab to Marketplace with a real-time pool activity feed.
    • Supports infinite scroll, search, and filters (type, asset, user, date).
    • Click any activity to view details and open transactions in an external explorer.
    • Clear, retry, and manual refresh controls with informative loading and empty states.
    • Toggle live updates on/off with visual status indicators.
    • Introduced an Analytics tab placeholder (“Coming Soon”).

- Add ActivityType enum for transaction types (supply, borrow, repay, liquidation, withdrawal)
- Add PoolActivity interface for transaction data structure
- Add ActivityFilters interface for filtering capabilities
- Add ActivityPagination interface for pagination support
- Add ActivityFeedState interface for component state management
- Add TransactionDetails interface for detailed transaction info
- Add component prop interfaces for ActivityIconProps, ActivityItemProps, ActivityFeedProps

This establishes the type system foundation for the pool activity feed feature.
- Add TransactionMonitoringHelper class for parsing and categorizing transactions
- Support for Stellar transaction parsing and conversion to PoolActivity
- Support for Blend Protocol event parsing and conversion
- Add activity type determination from transaction operations
- Add amount and timestamp formatting utilities
- Add activity icon and color mapping functions
- Add transaction hash validation
- Add activity filtering capabilities
- Add transaction details fetching (mock implementation)
- Add health factor calculation support

This helper provides the core logic for processing blockchain transactions
and converting them into user-friendly activity data.
- Add usePoolActivity hook with comprehensive state management
- Implement real-time data fetching with WebSocket/polling fallback
- Add filtering and pagination logic
- Add auto-refresh capabilities with configurable intervals
- Add error handling and retry mechanisms with exponential backoff
- Add connection management for WebSocket and polling
- Add mock API simulation for development
- Add intersection observer for infinite scroll
- Add debounced filter changes
- Add cleanup on component unmount

This hook provides the data layer for the activity feed with real-time
updates, filtering, pagination, and robust error handling.
- Add ActivityItem component with expandable transaction details
- Implement visual icons and color coding for different activity types
- Add user-friendly amount and timestamp formatting
- Add expandable details with transaction hash, block number, health factor
- Add links to Stellar Explorer for transaction verification
- Add copy hash functionality for easy sharing
- Add responsive design with proper spacing and typography
- Add click handlers for expand/collapse and external links
- Add proper TypeScript props interface
- Add accessibility features with proper ARIA labels

This component provides the individual transaction display with rich
interaction capabilities and detailed information access.
- Add ActivityFeed component with comprehensive filtering and search
- Implement real-time updates with WebSocket/polling fallback
- Add advanced filtering by activity type, asset, and search query
- Add infinite scroll with intersection observer for pagination
- Add loading states with skeleton components
- Add error handling with retry mechanisms
- Add empty state with helpful messaging
- Add real-time status indicators and toggle controls
- Add responsive design for mobile and desktop
- Add accessibility features and keyboard navigation
- Add performance optimizations with debounced updates
- Add proper TypeScript interfaces and error boundaries

This component provides the main activity feed interface with real-time
updates, advanced filtering, and excellent user experience.
- Add Activity Feed tab to existing marketplace interface
- Implement tab-based navigation with state management
- Add ActivityFeed component integration with pool ID
- Add real-time updates configuration (30s refresh interval)
- Add activity click handlers for user interaction
- Add analytics placeholder tab for future development
- Maintain existing marketplace functionality and layout
- Add proper imports and component integration
- Add responsive tab design consistent with existing UI
- Add proper TypeScript state management

This integration adds the activity feed as a new tab in the marketplace
while preserving all existing functionality and maintaining UI consistency.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Adds an end-to-end Activity Feed feature: new domain types, parsing/formatting helper, data hook with realtime/polling and pagination, UI components (feed and item), and Marketplace page integration via a new “Activity” tab.

Changes

Cohort / File(s) Summary of Changes
Domain Types
frontend/src/@types/activity.entity.ts
Introduces ActivityType enum and interfaces for PoolActivity, filters, pagination, feed state, transaction details, and component props.
UI Components
frontend/src/components/modules/marketplace/ui/components/ActivityFeed.tsx, frontend/src/components/modules/marketplace/ui/components/ActivityItem.tsx
Adds ActivityFeed with filters, infinite scroll, realtime toggle, error/loading states; adds ActivityItem for rendering individual activities with expand/details and explorer actions.
Hook (Data + Realtime)
frontend/src/hooks/usePoolActivity.ts
New hook managing activity state, pagination, filters, realtime (WS scaffold + polling), retries, and mock data fetching.
Parser/Helper
frontend/src/helpers/transaction-monitoring.helper.ts
Adds TransactionMonitoringHelper to parse Stellar transactions and Blend events into PoolActivity, provide formatting, validation, icon/color helpers, and filtering.
Page Integration
frontend/src/components/modules/marketplace/ui/pages/MarketplacePage.tsx
Adds tab state and renders new Activity tab containing ActivityFeed; includes placeholder Analytics tab.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant MarketplacePage
  participant ActivityFeed
  participant usePoolActivity
  participant Helper as TransactionMonitoringHelper
  participant API as Backend/API
  participant WS as WebSocket

  User->>MarketplacePage: Open page
  MarketplacePage->>ActivityFeed: Render Activity tab
  ActivityFeed->>usePoolActivity: init(options: poolId, filters, autoRefresh)
  usePoolActivity->>API: fetchActivities(page=1)
  API-->>usePoolActivity: activities (raw)
  usePoolActivity->>Helper: parse/format activities
  Helper-->>usePoolActivity: PoolActivity[]
  usePoolActivity-->>ActivityFeed: state {activities, loading=false, pagination}

  rect rgba(200,235,255,0.25)
    note right of usePoolActivity: Realtime enabled
    usePoolActivity-->>WS: connect (scaffold)
    WS-->>usePoolActivity: activity event (mock)
    usePoolActivity->>Helper: parse event
    Helper-->>usePoolActivity: PoolActivity
    usePoolActivity-->>ActivityFeed: append activity
  end

  User->>ActivityFeed: Scroll to bottom
  ActivityFeed->>usePoolActivity: loadMore()
  usePoolActivity->>API: fetchActivities(next page)
  API-->>usePoolActivity: activities
  usePoolActivity-->>ActivityFeed: append + update pagination

  alt Error
    API--x usePoolActivity: error
    usePoolActivity-->>ActivityFeed: state {error}
    User->>ActivityFeed: Retry
    ActivityFeed->>usePoolActivity: retry()
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • Feat/update UI #207 — Also modifies MarketplacePage; potential overlap/conflicts with new Activity tab integration and imports.

Suggested labels

codex

Poem

I thump my paws—new tabs appear,
A stream of hops: supply to cheer.
Events now bloom, in tidy rows,
With hashes, blocks, and status glows.
I nibble logs, then press “Refresh”—
Live burrow feeds, a bunny’s dash! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately summarizes the primary change by indicating the addition of a pool activity feed feature, matching the core implementation work in the PR. It is concise, specific, and directly related to the introduced functionality without extraneous details.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24617cf and 551f8e1.

📒 Files selected for processing (6)
  • frontend/src/@types/activity.entity.ts (1 hunks)
  • frontend/src/components/modules/marketplace/ui/components/ActivityFeed.tsx (1 hunks)
  • frontend/src/components/modules/marketplace/ui/components/ActivityItem.tsx (1 hunks)
  • frontend/src/components/modules/marketplace/ui/pages/MarketplacePage.tsx (4 hunks)
  • frontend/src/helpers/transaction-monitoring.helper.ts (1 hunks)
  • frontend/src/hooks/usePoolActivity.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
frontend/src/components/modules/marketplace/ui/components/ActivityItem.tsx (2)
frontend/src/@types/activity.entity.ts (1)
  • ActivityItemProps (66-71)
frontend/src/helpers/transaction-monitoring.helper.ts (4)
  • formatAmount (186-200)
  • formatTimestamp (205-219)
  • getActivityIcon (224-234)
  • getActivityColor (239-249)
frontend/src/components/modules/marketplace/ui/components/ActivityFeed.tsx (2)
frontend/src/@types/activity.entity.ts (2)
  • ActivityFeedProps (73-79)
  • ActivityFilters (23-30)
frontend/src/hooks/usePoolActivity.ts (1)
  • usePoolActivity (25-195)
frontend/src/hooks/usePoolActivity.ts (1)
frontend/src/@types/activity.entity.ts (3)
  • ActivityFilters (23-30)
  • ActivityFeedState (39-46)
  • PoolActivity (9-21)
frontend/src/components/modules/marketplace/ui/pages/MarketplacePage.tsx (1)
frontend/src/components/modules/marketplace/ui/components/ActivityFeed.tsx (1)
  • ActivityFeed (26-340)
frontend/src/helpers/transaction-monitoring.helper.ts (1)
frontend/src/@types/activity.entity.ts (2)
  • PoolActivity (9-21)
  • TransactionDetails (48-58)

Comment on lines +66 to +82
// Apply filters
const applyFilters = () => {
const filters: ActivityFilters = {
...initialFilters,
search: searchQuery || undefined,
type: selectedType !== 'all' ? selectedType : undefined,
asset: selectedAsset !== 'all' ? selectedAsset : undefined,
};

actions.setFilters(filters);
};

// Handle filter changes
useEffect(() => {
const timeoutId = setTimeout(applyFilters, 300);
return () => clearTimeout(timeoutId);
}, [searchQuery, selectedType, selectedAsset]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore support for initialFilters

applyFilters always overwrites whatever the parent passed in via initialFilters with undefined values because the local state boots with ''/'all'. On the first render the effect on Lines 79-82 fires, so we immediately send actions.setFilters({ …initialFilters, type: undefined, asset: undefined, search: undefined }). As a result the feed silently drops any pre-applied filters (e.g., a pool-specific asset or type restriction), breaking the contract of the filters prop. Please keep the original values unless the user actually chooses a different option—for example, clone initialFilters and only mutate keys when a concrete selection/search is present, or initialise the local state from initialFilters once.

🤖 Prompt for AI Agents
In frontend/src/components/modules/marketplace/ui/components/ActivityFeed.tsx
around lines 66 to 82, applyFilters currently spreads initialFilters but then
overwrites its keys with undefined because local state defaults ('', 'all') are
treated as “no selection”; this causes pre-applied parent filters to be lost on
first render. Fix by preserving initialFilters values unless the user has made a
concrete selection: either initialize local
searchQuery/selectedType/selectedAsset from initialFilters on mount, or when
building filters clone initialFilters and only set search/type/asset when their
local state is a real value (non-empty and not 'all'); then call
actions.setFilters with that merged object. Ensure the effect uses appropriate
dependencies so it doesn't clear initialFilters on first render.

Comment on lines +102 to +112
variant="ghost"
size="sm"
onClick={handleExpand}
className="p-1"
>
{isExpanded ? (
<ChevronUp className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)}
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop bubbling from the expand chevron.
Because the card itself toggles expansion on click, the chevron button fires handleExpand twice (its own handler plus the bubbled card click), so the row never opens. Prevent bubbling before toggling.

Apply this diff:

           <Button
             variant="ghost"
             size="sm"
-            onClick={handleExpand}
+            onClick={(e) => {
+              e.stopPropagation();
+              handleExpand();
+            }}
             className="p-1"
           >
🤖 Prompt for AI Agents
In frontend/src/components/modules/marketplace/ui/components/ActivityItem.tsx
around lines 102 to 112, the chevron button's onClick bubbles up to the parent
card causing handleExpand to be invoked twice; fix by changing the chevron's
click handler to accept the click event, call event.stopPropagation() (and
optionally event.preventDefault() if needed), then call handleExpand so the
toggle only runs once.

Comment on lines +178 to +183
variant="outline"
size="sm"
onClick={() => navigator.clipboard.writeText(activity.transactionHash)}
>
Copy Hash
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep “Copy Hash” from collapsing the row.
Copying the transaction hash currently bubbles to the card click handler and collapses the details you’re trying to read. Stop propagation so utility actions don’t fight the expand/collapse UX.

Apply this diff:

             <Button
               variant="outline"
               size="sm"
-              onClick={() => navigator.clipboard.writeText(activity.transactionHash)}
+              onClick={(e) => {
+                e.stopPropagation();
+                navigator.clipboard.writeText(activity.transactionHash);
+              }}
             >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
variant="outline"
size="sm"
onClick={() => navigator.clipboard.writeText(activity.transactionHash)}
>
Copy Hash
</Button>
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation();
navigator.clipboard.writeText(activity.transactionHash);
}}
>
Copy Hash
</Button>
🤖 Prompt for AI Agents
In frontend/src/components/modules/marketplace/ui/components/ActivityItem.tsx
around lines 178 to 183 the "Copy Hash" button click bubbles up to the card's
click handler and collapses the row; modify the button's onClick to accept the
click event, call event.stopPropagation() (and optionally
event.preventDefault()) before calling
navigator.clipboard.writeText(activity.transactionHash) so the copy action does
not trigger the parent expand/collapse behavior.

Comment on lines +99 to +120
private static determineActivityType(tx: StellarTransaction): ActivityType | null {
const operations = tx.operations;

for (const op of operations) {
const opType = op.type.toLowerCase();

if (this.BLEND_EVENT_TYPES.SUPPLY.some(type => opType.includes(type))) {
return ActivityType.SUPPLY;
}
if (this.BLEND_EVENT_TYPES.BORROW.some(type => opType.includes(type))) {
return ActivityType.BORROW;
}
if (this.BLEND_EVENT_TYPES.REPAY.some(type => opType.includes(type))) {
return ActivityType.REPAY;
}
if (this.BLEND_EVENT_TYPES.LIQUIDATION.some(type => opType.includes(type))) {
return ActivityType.LIQUIDATION;
}
if (this.BLEND_EVENT_TYPES.WITHDRAWAL.some(type => opType.includes(type))) {
return ActivityType.WITHDRAWAL;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Transaction parsing never produces an ActivityType

The detection logic here assumes that StellarOperation.type contains keywords like “supply”, “borrow”, etc. In practice the Horizon API only ever surfaces canonical operation names (payment, path_payment_strict_send, manage_sell_offer, …). Because none of those include the Blend keywords, determineActivityType always returns null, which makes parseStellarTransaction bail out and yields zero activities from real Stellar transactions—a critical functional break. You’ll need to derive the activity type from reliable fields (e.g., memo data, operation details, attached metadata) instead of the generic Horizon type string.

🤖 Prompt for AI Agents
In frontend/src/helpers/transaction-monitoring.helper.ts around lines 99-120,
the current logic matches Blend activity keywords against Horizon's generic
operation.type (e.g., "payment") which never contains those keywords, so
determineActivityType always returns null; update the function to derive
ActivityType from reliable sources: inspect tx.memo (and decode if base64/hex)
for Blend event tags, and for each operation switch on op.type and examine
operation-specific fields (e.g., payment: asset/amount/to/from,
manage_sell_offer/manage_buy_offer: offer IDs and assets, manage_data or
invoke_host_function: custom keys/arguments, claimable_balance or account_merge
metadata) to detect Blend semantics, then map those concrete patterns to
ActivityType.SUPPLY/BORROW/REPAY/LIQUIDATION/WITHDRAWAL; add a clear mapping
table (or switch/case) and fallbacks (memo first, then op-specific checks) and
keep returning null only if no concrete match is found.

Comment on lines +48 to +109
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const retryCountRef = useRef(0);
const maxRetries = 3;

// WebSocket connection for real-time updates
const connectWebSocket = useCallback(() => {
if (!poolId || wsRef.current?.readyState === WebSocket.OPEN) return;

try {
// This would connect to a real WebSocket endpoint
// For now, we'll simulate with polling
console.log('WebSocket connection would be established here');
} catch (error) {
console.error('WebSocket connection failed:', error);
setState(prev => ({ ...prev, error: 'Real-time connection failed' }));
}
}, [poolId]);

// Polling fallback for real-time updates
const startPolling = useCallback(() => {
if (intervalRef.current) clearInterval(intervalRef.current);

if (state.realTimeEnabled) {
intervalRef.current = setInterval(() => {
fetchActivities();
}, refreshInterval);
}
}, [state.realTimeEnabled, refreshInterval]);

// Fetch activities from API
const fetchActivities = useCallback(async (page = 1, append = false) => {
if (state.loading) return;

setState(prev => ({ ...prev, loading: true, error: null }));

try {
// Simulate API call - in real implementation, this would call the actual API
const mockActivities = await simulateFetchActivities(poolId, page, state.filters);

setState(prev => ({
...prev,
activities: append ? [...prev.activities, ...mockActivities] : mockActivities,
loading: false,
pagination: {
...prev.pagination,
page,
total: mockActivities.length * 10, // Mock total
hasMore: mockActivities.length === pageSize
}
}));

retryCountRef.current = 0;
} catch (error) {
console.error('Error fetching activities:', error);
setState(prev => ({
...prev,
loading: false,
error: error instanceof Error ? error.message : 'Failed to fetch activities'
}));
}
}, [poolId, state.filters, pageSize]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix stale loading guard in fetchActivities.
Because fetchActivities is memoized with [poolId, state.filters, pageSize], the state.loading value it reads at Line 80 is whatever was true when the callback was created. After the first request flips loading to true, the interval/polling loop and loadMore still see false and fire again, creating overlapping requests and inconsistent pagination. Please gate the fetch on a ref that tracks the live loading flag before issuing the call.

Apply this diff to fix the issue:

@@
-  const intervalRef = useRef<NodeJS.Timeout | null>(null);
-  const wsRef = useRef<WebSocket | null>(null);
+  const intervalRef = useRef<NodeJS.Timeout | null>(null);
+  const wsRef = useRef<WebSocket | null>(null);
+  const loadingRef = useRef(false);
@@
-    if (state.loading) return;
-
-    setState(prev => ({ ...prev, loading: true, error: null }));
+    if (loadingRef.current) return;
+
+    loadingRef.current = true;
+    setState(prev => ({ ...prev, loading: true, error: null }));
@@
-      retryCountRef.current = 0;
+      retryCountRef.current = 0;
@@
-      setState(prev => ({
+      setState(prev => ({
         ...prev,
         loading: false,
         error: error instanceof Error ? error.message : 'Failed to fetch activities'
       }));
-    }
+    } finally {
+      loadingRef.current = false;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const retryCountRef = useRef(0);
const maxRetries = 3;
// WebSocket connection for real-time updates
const connectWebSocket = useCallback(() => {
if (!poolId || wsRef.current?.readyState === WebSocket.OPEN) return;
try {
// This would connect to a real WebSocket endpoint
// For now, we'll simulate with polling
console.log('WebSocket connection would be established here');
} catch (error) {
console.error('WebSocket connection failed:', error);
setState(prev => ({ ...prev, error: 'Real-time connection failed' }));
}
}, [poolId]);
// Polling fallback for real-time updates
const startPolling = useCallback(() => {
if (intervalRef.current) clearInterval(intervalRef.current);
if (state.realTimeEnabled) {
intervalRef.current = setInterval(() => {
fetchActivities();
}, refreshInterval);
}
}, [state.realTimeEnabled, refreshInterval]);
// Fetch activities from API
const fetchActivities = useCallback(async (page = 1, append = false) => {
if (state.loading) return;
setState(prev => ({ ...prev, loading: true, error: null }));
try {
// Simulate API call - in real implementation, this would call the actual API
const mockActivities = await simulateFetchActivities(poolId, page, state.filters);
setState(prev => ({
...prev,
activities: append ? [...prev.activities, ...mockActivities] : mockActivities,
loading: false,
pagination: {
...prev.pagination,
page,
total: mockActivities.length * 10, // Mock total
hasMore: mockActivities.length === pageSize
}
}));
retryCountRef.current = 0;
} catch (error) {
console.error('Error fetching activities:', error);
setState(prev => ({
...prev,
loading: false,
error: error instanceof Error ? error.message : 'Failed to fetch activities'
}));
}
}, [poolId, state.filters, pageSize]);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const loadingRef = useRef(false);
const retryCountRef = useRef(0);
const maxRetries = 3;
// WebSocket connection for real-time updates
const connectWebSocket = useCallback(() => {
if (!poolId || wsRef.current?.readyState === WebSocket.OPEN) return;
try {
// This would connect to a real WebSocket endpoint
// For now, we'll simulate with polling
console.log('WebSocket connection would be established here');
} catch (error) {
console.error('WebSocket connection failed:', error);
setState(prev => ({ ...prev, error: 'Real-time connection failed' }));
}
}, [poolId]);
// Polling fallback for real-time updates
const startPolling = useCallback(() => {
if (intervalRef.current) clearInterval(intervalRef.current);
if (state.realTimeEnabled) {
intervalRef.current = setInterval(() => {
fetchActivities();
}, refreshInterval);
}
}, [state.realTimeEnabled, refreshInterval]);
// Fetch activities from API
const fetchActivities = useCallback(async (page = 1, append = false) => {
if (loadingRef.current) return;
loadingRef.current = true;
setState(prev => ({ ...prev, loading: true, error: null }));
try {
// Simulate API call - in real implementation, this would call the actual API
const mockActivities = await simulateFetchActivities(poolId, page, state.filters);
setState(prev => ({
...prev,
activities: append ? [...prev.activities, ...mockActivities] : mockActivities,
loading: false,
pagination: {
...prev.pagination,
page,
total: mockActivities.length * 10, // Mock total
hasMore: mockActivities.length === pageSize
}
}));
retryCountRef.current = 0;
} catch (error) {
console.error('Error fetching activities:', error);
setState(prev => ({
...prev,
loading: false,
error: error instanceof Error ? error.message : 'Failed to fetch activities'
}));
} finally {
loadingRef.current = false;
}
}, [poolId, state.filters, pageSize]);
🤖 Prompt for AI Agents
In frontend/src/hooks/usePoolActivity.ts around lines 48 to 109, the memoized
fetchActivities reads state.loading (stale inside the callback) causing
overlapping requests; replace that guard with a live ref. Add a loadingRef =
useRef(false) and check loadingRef.current at the top of fetchActivities; when
starting a request set loadingRef.current = true and setState loading:true, and
on both success and error set loadingRef.current = false and setState
loading:false. Ensure fetchActivities no longer depends on state.loading (keep
dependencies poolId, state.filters, pageSize) so it always uses the live
loadingRef to gate concurrent calls.

Comment on lines +125 to +137
const setFilters = useCallback((filters: ActivityFilters) => {
setState(prev => ({ ...prev, filters }));
// Debounce filter changes
setTimeout(() => {
fetchActivities(1, false);
}, 300);
}, [fetchActivities]);

// Clear all filters
const clearFilters = useCallback(() => {
setState(prev => ({ ...prev, filters: {} }));
fetchActivities(1, false);
}, [fetchActivities]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Debounce filters with the latest criteria.
The timeout inside setFilters reuses the old fetchActivities closure, so the request still runs with the previous state.filters. Nothing clears the timeout either, so rapid filter tweaks queue several stale fetches. Keep the current filters in a ref, clear the pending timeout before scheduling, and make fetchActivities consume the ref so it always sees the latest filters.

Apply this diff:

@@
-  const intervalRef = useRef<NodeJS.Timeout | null>(null);
-  const wsRef = useRef<WebSocket | null>(null);
-  const retryCountRef = useRef(0);
-  const maxRetries = 3;
+  const intervalRef = useRef<NodeJS.Timeout | null>(null);
+  const wsRef = useRef<WebSocket | null>(null);
+  const loadingRef = useRef(false);
+  const filtersRef = useRef<ActivityFilters>(initialFilters);
+  const filterTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const retryCountRef = useRef(0);
+  const maxRetries = 3;
@@
-      const mockActivities = await simulateFetchActivities(poolId, page, state.filters);
+      const mockActivities = await simulateFetchActivities(poolId, page, filtersRef.current);
@@
-  }, [poolId, state.filters, pageSize]);
+  }, [poolId, pageSize]);
@@
-  const setFilters = useCallback((filters: ActivityFilters) => {
-    setState(prev => ({ ...prev, filters }));
-    // Debounce filter changes
-    setTimeout(() => {
-      fetchActivities(1, false);
-    }, 300);
-  }, [fetchActivities]);
+  const setFilters = useCallback((filters: ActivityFilters) => {
+    filtersRef.current = filters;
+    setState(prev => ({ ...prev, filters }));
+    if (filterTimeoutRef.current) {
+      clearTimeout(filterTimeoutRef.current);
+    }
+    filterTimeoutRef.current = setTimeout(() => {
+      fetchActivities(1, false);
+    }, 300);
+  }, [fetchActivities]);
@@
-  const clearFilters = useCallback(() => {
-    setState(prev => ({ ...prev, filters: {} }));
+  const clearFilters = useCallback(() => {
+    filtersRef.current = {};
+    setState(prev => ({ ...prev, filters: {} }));
     fetchActivities(1, false);
   }, [fetchActivities]);
@@
   useEffect(() => {
     return () => {
       if (intervalRef.current) clearInterval(intervalRef.current);
       if (wsRef.current) wsRef.current.close();
+      if (filterTimeoutRef.current) clearTimeout(filterTimeoutRef.current);
     };
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const setFilters = useCallback((filters: ActivityFilters) => {
setState(prev => ({ ...prev, filters }));
// Debounce filter changes
setTimeout(() => {
fetchActivities(1, false);
}, 300);
}, [fetchActivities]);
// Clear all filters
const clearFilters = useCallback(() => {
setState(prev => ({ ...prev, filters: {} }));
fetchActivities(1, false);
}, [fetchActivities]);
const setFilters = useCallback((filters: ActivityFilters) => {
filtersRef.current = filters;
setState(prev => ({ ...prev, filters }));
if (filterTimeoutRef.current) {
clearTimeout(filterTimeoutRef.current);
}
filterTimeoutRef.current = setTimeout(() => {
fetchActivities(1, false);
}, 300);
}, [fetchActivities]);
// Clear all filters
const clearFilters = useCallback(() => {
filtersRef.current = {};
setState(prev => ({ ...prev, filters: {} }));
fetchActivities(1, false);
}, [fetchActivities]);
🤖 Prompt for AI Agents
In frontend/src/hooks/usePoolActivity.ts around lines 125 to 137, the setFilters
debounce schedules fetchActivities via a timeout that closes over stale
state.filters and never clears previous timers; replace this by storing the
latest filters in a useRef (e.g., filtersRef.current = filters), store the
timeout id in another ref, call clearTimeout on that ref before scheduling a new
setTimeout, and have fetchActivities read filters from filtersRef (or accept
filters param sourced from the ref) so the debounced call always uses current
criteria; also clear the timeout in a useEffect cleanup/unmount to avoid stray
calls.

@JosueBrenes
Copy link
Contributor

Hi @respp
Great job!!!
Resolve conflicts please

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments